#pragma once

#include "concepts.h"
#include <coroutine>
#include <exception>
#include <source_location>
#include <system_error>

namespace cobt {


class PreviousPromise {
public:
  auto get_return_object() noexcept {
    return std::coroutine_handle<PreviousPromise>::from_promise(*this);
  }
  // 协程开始执行时调用并 co_await 它的结果。suspend_always 始终暂停
  auto initial_suspend() noexcept { return std::suspend_always{}; }

  // 协程抵达 co_return 时, 调用并 co_await 它的结果
  auto final_suspend() noexcept { return PreviousAwaiter{m_previous}; }

  // 对于 co_return expr;其中 expr 具有非 void 类型时，调用
  // promise.return_value(expr)
  void return_value(std::coroutine_handle<> previous) noexcept {
    m_previous = previous;
  }

  void unhandled_exception() {}

private:
  std::coroutine_handle<> m_previous;
  PreviousPromise &operator=(PreviousPromise &&) = delete;
};

template <class T> struct Promise {
  auto initial_suspend() { return std::suspend_always(); }
  auto final_suspend() noexcept { return PreviousAwaiter(m_previous); }
  auto get_return_object() {
    return std::coroutine_handle<Promise>::from_promise(*this);
  }
  void unhandled_exception() {
    // std::cout << "unhandled_exception" << std::endl;
    m_exception = std::current_exception();
    std::rethrow_exception(m_exception);
  }
  void return_value(T &&val) { m_result.put(std::move(val)); }
  void return_value(const T &val) { m_result.put(val); }
  T result() {
    if (m_exception) {
      std::rethrow_exception(m_exception);
    }
    return m_result.move();
  }

  std::coroutine_handle<> m_previous;
  std::exception_ptr m_exception;

  Promise &operator=(Promise &&) = delete;
  UnInit<T> m_result;
};

template <> struct Promise<void> {
  auto initial_suspend() { return std::suspend_always(); }
  auto final_suspend() noexcept { return PreviousAwaiter(m_previous); }
  auto get_return_object() {
    return std::coroutine_handle<Promise>::from_promise(*this);
  }
  void unhandled_exception() {
    m_exception = std::current_exception();
    std::rethrow_exception(m_exception);
    // std::cout << "unhandled_exception" << std::endl;
  }
  void return_void() { m_exception = nullptr; }
  auto result() {
    if (m_exception) {
      std::rethrow_exception(m_exception);
    }
    return NonVoidHelper<>{};
  }
  std::coroutine_handle<> m_previous;
  std::exception_ptr m_exception;
  Promise &operator=(Promise &&) = delete;
};

template <class T = void, class P = Promise<T>>
class [[nodiscard("no co_await")]] Task {
public:
  using promise_type = P;
  Task(std::coroutine_handle<promise_type> co_handle) { m_handle = co_handle; }
  Task(Task &&that) : m_handle(that.m_handle) { that.m_handle = nullptr; };
  ~Task() {
    if (m_handle) {
      m_handle.destroy();
    }
  }
  auto operator co_await() const noexcept { return Awaiter{m_handle}; }
  operator std::coroutine_handle<>() const noexcept { return m_handle; }
  auto &get_handle() noexcept { return m_handle; }
  struct Awaiter {
    bool await_ready() const noexcept { return false; }
    std::coroutine_handle<>
    await_suspend(std::coroutine_handle<> coroutine) const noexcept {
      m_handle.promise().m_previous = coroutine;
      return m_handle;
    }
    auto await_resume() const { return m_handle.promise().result(); }
    std::coroutine_handle<promise_type> m_handle;
  };

  std::coroutine_handle<promise_type> m_handle;
};

using PreHandleTask = Task<void, PreviousPromise>;
inline auto
check_error(auto res,
            const std::source_location &loc = std::source_location::current()) {
  if (res == -1) [[unlikely]] {
    throw std::system_error(errno, std::system_category(),
                            std::string(loc.file_name()) + ":" +
                                std::to_string(loc.line()));
  }
  return res;
}

template <class P> struct GetCoroutine {
  bool await_ready() const noexcept { return false; }
  std::coroutine_handle<P>
  await_suspend(std::coroutine_handle<P> pre) noexcept {
    m_previous = pre;
    return pre;
  }
  std::coroutine_handle<P> await_resume() noexcept { return m_previous; }
  std::coroutine_handle<P> m_previous;
};

} // namespace cobt
